﻿/**
 * @copyright Copyright 2009 Structured Solutions
 * @license http://www.codeplex.com/ShippingAgent/license Microsoft Public License (Ms-PL)
 * @version 3.7
 */

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Globalization;
using System.Web.Services;
using System.Web.Services.Protocols;
using Microsoft.CommerceServer;
using Microsoft.CommerceServer.Catalog;
using Microsoft.CommerceServer.Orders;
using Microsoft.CommerceServer.Runtime.Configuration;
using Microsoft.CommerceServer.Runtime.Diagnostics;
using Microsoft.CommerceServer.Runtime.Orders;
using Microsoft.CommerceServer.Runtime.Profiles;
using CatalogSiteAgent=Microsoft.CommerceServer.Catalog.CatalogSiteAgent;

namespace StructuredSolutions.ShippingAgent
{
    [WebService(Namespace = "http://www.codeplex.com/ShippingAgent",
        Description = "Shipping Agent for CommerceServer 2007 and 2009")]
    public class ShippingAgentImpl : ShippingAgentService
    {
        public const string VERSION = "3.7.0";

        #region WebMethod Implementations

        [WebMethod(Description = "Retrieve the features supported by this implementation of Shipping Agent.")]
        public override Features GetFeatures()
        {
            try
            {
                return new Features
                {
                    CanSearchByOrderDate = true,
                    CanSearchByOrderNumber = true,
                    CanSearchByOrderStatus = true,
                    CanSearchByPaymentStatus = false
                };
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve features.", SoapException.ServerFaultCode, ex);
            }
        }

        [WebMethod(Description = "Retrieve orders created or updated since pageKey.")]
        public override OrderPage GetNewOrders(string orderStatusList, string paymentStatusList, string pageKey, int maxCount)
        {
            try
            {
                return GetNewAndUpdatedOrdersFromStore(orderStatusList, pageKey, maxCount);
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve new and updated orders.", SoapException.ServerFaultCode, ex);
            }
        }

        /// <summary>
        /// Return one order.
        /// </summary>
        /// <param name="orderId">The ID of the order.</param>
        /// <returns>The OrderResult.</returns>
        /// <exception cref="SoapException">If the orderID is not valid (such as blank or null).</exception>
        [WebMethod(Description = "Retrieve one order.")]
        public override OrderResult GetOrder(string orderId)
        {
            try
            {
                if (string.IsNullOrEmpty(orderId))
                    throw new ApplicationException("Missing order ID.");
                return GetOrderFromStore(orderId);
            }
            catch (ApplicationException ex)
            {
                var result = new OrderResult();
                result.Success = false;
                result.Message = ex.Message;
                return result;
            }
            catch (Exception ex)
            {
                var result = new OrderResult();
                result.Success = false;
                result.Message = ex.ToString();
                return result;
            }
        }

        /// <summary>
        /// Return all the orders that fall on and within the specified ranges.
        /// </summary>
        /// <param name="startOrderNumber">The starting order number; or null or an empty string to start at the first order number in the store.</param>
        /// <param name="endOrderNumber">The ending order number; or null or an empty string to end with the last order number in the store.</param>
        /// <param name="startOrderDate">The starting order date. If no start date is specified, start from the earliest order.</param>
        /// <param name="startOrderDateSpecified">True if startOrderDate is specified, false otherwise.</param>
        /// <param name="endOrderDate">The ending order date. If no end date is specified, end with the most recent order.</param>
        /// <param name="endOrderDateSpecified">True if endOrderDate is specified, false otherwise.</param>
        /// <param name="paymentStatusList"></param>
        /// <param name="pageKey">The last order number from the previous result; or null or an empty string to ignore this parameter.</param>
        /// <param name="maxCount">The maximum number of orders to return.</param>
        /// <param name="orderStatusList"></param>
        /// <returns>Up to maxCount Order's, sorted by order number; or an empty list or null 
        /// if there are no orders that fall on and within the specified ranges.</returns>
        /// <remarks>If maxCount orders are returned, the Shipper client will call this method again with the 
        /// same ranges and with lastOrderNumber set to the last order number in the previous result. In this
        /// case, use lastOrderNumber to calculate an appropriate new startOrderNumber (for example,
        /// startOrderNumber = lastOrderNumber + 1).
        /// </remarks>
        [WebMethod(Description = "Return the list of orders that fall on and within the specified ranges.")]
        public override OrderPage GetOrdersByRange(string startOrderNumber, string endOrderNumber, DateTime startOrderDate, bool startOrderDateSpecified, DateTime endOrderDate, bool endOrderDateSpecified, string orderStatusList, string paymentStatusList, string pageKey, int maxCount)
        {
            try
            {
                var nullableStartDate = ShippingAgentHelper.ConvertWebServiceNullable(startOrderDate, startOrderDateSpecified);
                var nullableEndDate = ShippingAgentHelper.ConvertWebServiceNullable(endOrderDate, endOrderDateSpecified);
                return GetOrdersByRangeFromStore(startOrderNumber, endOrderNumber, nullableStartDate, nullableEndDate, orderStatusList, paymentStatusList, pageKey, maxCount);
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve orders by range.", SoapException.ServerFaultCode, ex);
            }
        }

        [WebMethod(Description = "Retrieve the list of all searchable order statuses.")]
        public override OrderStatus[] GetOrderStatuses()
        {
            try
            {
                var statuses = new List<OrderStatus>();
                using (var allowedStatus = OrderContext.GetAllowedStatus())
                {
                    if (allowedStatus == null) 
                        return null;

                    var allowedStatusTable = allowedStatus.Tables["AllowedStatusTable"];
                    if (allowedStatusTable == null)
                        return null;

                    foreach (DataRow row in allowedStatusTable.Rows)
                    {
                        statuses.Add(
                            new OrderStatus
                                {
                                    Name = ShippingAgentHelper.SplitCompoundName(Convert.ToString(row[0])),
                                    OrderStatusId = Convert.ToString(row[0])
                                });
                    }
                }
                return statuses.ToArray();
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve order statuses.", SoapException.ServerFaultCode, ex);
            }
        }

        /// <summary>
        /// Return one specific package.
        /// </summary>
        /// <param name="orderId">The ID of the order.</param>
        /// <param name="packageId">The ID of the package.</param>
        /// <returns>A PackageResult.</returns>
        /// <exception cref="SoapException">If the orderId or packageId is not valid (such as blank or null).</exception>
        [WebMethod(Description = "Retrieve one package.")]
        public override PackageResult GetPackage(string orderId, string packageId)
        {
            try
            {
                if (string.IsNullOrEmpty(orderId))
                    throw new ApplicationException("Invalid order ID.");
                if (string.IsNullOrEmpty(packageId))
                    throw new ApplicationException("Invalid package ID.");
                return GetPackageFromStore(orderId, packageId);
            }
            catch (ApplicationException ex)
            {
                var result = new PackageResult();
                result.Success = false;
                result.Message = ex.Message;
                return result;
            }
            catch (Exception ex)
            {
                var result = new PackageResult();
                result.Success = false;
                result.Message = ex.ToString();
                return result;
            }
        }

        [WebMethod(Description = "Retrieve the list of all searchable order payment statuses.")]
        public override PaymentStatus[] GetPaymentStatuses()
        {
            // Cannot search by payment status in this build
            return null;
        }

        /// <summary>
        /// Returns the complete list of shipping rate providers offered by the store.
        /// These are used to create criteria based on the rate provider or rate
        /// option.
        /// </summary>
        /// <returns>The list of RateProviders's.</returns>
        [WebMethod(Description = "Retrieve the list of shipping rate providers.")]
        public override RateProvider[] GetRateProviders()
        {
            try
            {
                return GetRateProvidersFromStore();
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve rate providers.", SoapException.ServerFaultCode, ex);
            }
        }

        /// <summary>
        /// Returns the complete list of tracking providers supported by the store. The
        /// Shipper client may assign any tracking provider to a package, regardless of
        /// how the shipping cost was initially calculated.
        /// </summary>
        /// <returns>The list of TrackingProvider's.</returns>
        [WebMethod(Description = "Retrieve the list of package tracking providers.")]
        public override TrackingProvider[] GetTrackingProviders()
        {
            try
            {
                return GetTrackingProvidersFromStore();
            }
            catch (ApplicationException ex)
            {
                throw new DetailedSoapException(ex.Message, SoapException.ServerFaultCode, ex);
            }
            catch (Exception ex)
            {
                throw new DetailedSoapException("Cannot retrieve tracking providers.", SoapException.ServerFaultCode, ex);
            }
        }

        /// <summary>
        /// Return the ShippingAgent.wsdl implemented by this ShippingAgent.
        /// </summary>
        /// <returns>A string value that represents the version using the format
        /// Major.Minor.Revision. For example, "3.0.0".</returns>
        [WebMethod(Description = "Retrieve the WSDL interface version implemented by this Shipping Agent.")]
        public override string GetVersion()
        {
            return VERSION;
        }

        /// <summary>
        /// Record package information.
        /// </summary>
        /// <param name="update">The package information to record.</param>
        /// <returns>If the update is successful, the UpdateResult contains a fresh
        /// copy of the Order that contains the package. If the update is not
        /// successful, the UpdateResult contains the reason why the update failed.</returns>
        /// <remarks>The Shipper client may not include all Update elements. null
        /// elements should be ignored. If the ShipDate is included, then the package
        /// should be marked shipped.</remarks>
        [WebMethod(Description = "Record package information.")]
        public override UpdateResult UpdatePackage(Update update)
        {
            try
            {
                if (update == null)
                    return null;
                if (string.IsNullOrEmpty(update.OrderId))
                    throw new ApplicationException("Invalid order ID.");
                if (string.IsNullOrEmpty(update.PackageId))
                    throw new ApplicationException("Invalid package ID.");
                return UpdatePackageInStore(update);
            }
            catch (ApplicationException ex)
            {
                var result = new UpdateResult();
                result.Success = false;
                result.Message = ex.Message;
                return result;
            }
            catch (Exception ex)
            {
                var result = new UpdateResult();
                result.Success = false;
                result.Message = ex.ToString();
                return result;
            }
        }

        #endregion

        #region Store Specific Implementations

        private static OrderPage GetNewAndUpdatedOrdersFromStore(string orderStatusList, string pageKey, int maxCount)
        {
            var manager = OrderManagementContext.PurchaseOrderManager;
            var entities = manager.GetSearchableProperties(CultureInfo.CurrentUICulture.ToString());
            var factory = manager.GetSearchClauseFactory(entities, "PurchaseOrder");

            // If pageKey is missing, then return orders from today.
            SearchClause searchClause;
            if (string.IsNullOrEmpty(pageKey))
            {
                searchClause = factory.CreateClause(ExplicitComparisonOperator.OnOrAfter, "Created", DateTime.Today);
            }
            else
            {
                long binaryDate;
                if (!long.TryParse(pageKey, out binaryDate))
                    throw new ApplicationException(string.Format("Invalid paging key '{0}'.", pageKey));

                searchClause = factory.CreateClause(ExplicitComparisonOperator.After, "LastModified", DateTime.FromBinary(binaryDate));
            }

            if (!string.IsNullOrEmpty(orderStatusList))
            {
                var statusList = orderStatusList.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
                var statusClause = factory.CreateClause(ExplicitComparisonOperator.Equal, "Status", statusList[0]);
                for (var index = 1; index < statusList.Length; index++)
                {
                    statusClause = factory.UnionClauses(statusClause, factory.CreateClause(ExplicitComparisonOperator.Equal, "Status", statusList[index]));
                }
                searchClause = factory.IntersectClauses(searchClause, statusClause);
            }

            var searchOptions = new Microsoft.CommerceServer.SearchOptions
            {
                PropertiesToReturn = "OrderGroupId,SoldToId,LastModified",
                NumberOfRecordsToReturn = maxCount,
                SortDescending = false,
                SortProperties = "LastModified"
            };

            var orderIds = new List<string>();
            int recordsMatched;
            var lastUpdated = DateTime.UtcNow;
            using (var ds = manager.SearchPurchaseOrders(searchClause, searchOptions, out recordsMatched))
            {
                foreach (DataRow row in ds.Tables["PurchaseOrders"].Rows)
                {
                    var userId = (Guid)row["SoldToId"];
                    var orderGroupId = (Guid)row["OrderGroupId"];
                    lastUpdated = (DateTime) row["LastModified"];
                    var csPurchaseOrder = OrderContext.GetPurchaseOrder(userId, orderGroupId);
                    foreach (OrderForm csOrderForm in csPurchaseOrder.OrderForms)
                    {
                        orderIds.Add(BuildOrderId(csOrderForm));
                    }
                }
            }

            if (orderIds.Count == 0)
                return null;

            var result = new OrderPage();
            result.Key = lastUpdated.ToBinary().ToString();
            result.Orders = orderIds.ToArray();
            result.Remaining = recordsMatched - orderIds.Count;
            return result;
        }

        protected static OrderResult GetOrderFromStore(string orderId)
        {
            Guid csUserId;
            Guid csOrderGroupId;
            Guid csOrderFormId;
            try
            {
                var csIds = orderId.Split(',');
                if (csIds.Length < 3)
                    throw new ApplicationException("Invalid order ID.");

                csUserId = new Guid(csIds[0]);
                csOrderGroupId = new Guid(csIds[1]);
                csOrderFormId = new Guid(csIds[2]);
            }
            catch (FormatException)
            {
                throw new ApplicationException("Invalid order ID.");
            }
            catch (OverflowException)
            {
                throw new ApplicationException("Invalid order ID.");
            }

            var csPurchaseOrder = OrderContext.GetPurchaseOrder(csUserId, csOrderGroupId);
            if (csPurchaseOrder == null)
                throw new ApplicationException("Invalid user or purchase order ID.");

            var index = csPurchaseOrder.OrderForms.IndexOf(csOrderFormId);
            if (index == -1)
                throw new ApplicationException("Invalid order form ID.");

            return BuildOrderResult(csPurchaseOrder.OrderForms[index]);
        }

        protected static OrderPage GetOrdersByRangeFromStore(string startOrderNumber, string endOrderNumber, DateTime? startOrderDate, DateTime? endOrderDate, string orderStatusList, string paymentStatusList, string pageKey, int maxCount)
        {
            var manager = OrderManagementContext.PurchaseOrderManager;
            var entities = manager.GetSearchableProperties(CultureInfo.CurrentUICulture.ToString());
            var factory = manager.GetSearchClauseFactory(entities, "PurchaseOrder");
            SearchClause searchClause = null;

            if (!string.IsNullOrEmpty(startOrderNumber))
            {
                var greaterThan = factory.CreateClause(ExplicitComparisonOperator.GreaterThan, "TrackingNumber", startOrderNumber);
                var equalTo = factory.CreateClause(ExplicitComparisonOperator.Equal, "TrackingNumber", startOrderNumber);
                searchClause = factory.UnionClauses(greaterThan, equalTo);
            }

            if (!string.IsNullOrEmpty(endOrderNumber))
            {
                var lessThan = factory.CreateClause(ExplicitComparisonOperator.LessThan, "TrackingNumber", endOrderNumber);
                var equalTo = factory.CreateClause(ExplicitComparisonOperator.Equal, "TrackingNumber", endOrderNumber);
                var end = factory.UnionClauses(lessThan, equalTo);
                searchClause = searchClause == null ? end : factory.IntersectClauses(searchClause, end);
            }

            if (startOrderDate.HasValue)
            {
                var start = factory.CreateClause(ExplicitComparisonOperator.OnOrAfter, "Created", startOrderDate.Value);
                searchClause = searchClause == null ? start : factory.IntersectClauses(searchClause, start);
            }

            if (endOrderDate.HasValue)
            {
                var end = factory.CreateClause(ExplicitComparisonOperator.OnOrAfter, "Created", endOrderDate.Value);
                searchClause = searchClause == null ? end : factory.IntersectClauses(searchClause, end);
            }

            if (!string.IsNullOrEmpty(orderStatusList))
            {
                var statusList = orderStatusList.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
                var statusClause = factory.CreateClause(ExplicitComparisonOperator.Equal, "Status", statusList[0]);
                for (var index = 1; index < statusList.Length; index++)
                {
                    statusClause = factory.UnionClauses(statusClause, factory.CreateClause(ExplicitComparisonOperator.Equal, "Status", statusList[index]));
                }
                searchClause = searchClause == null ? statusClause : factory.IntersectClauses(searchClause, statusClause);
            }

            // Support paging
            if (!string.IsNullOrEmpty(pageKey))
            {
                var start = factory.CreateClause(ExplicitComparisonOperator.GreaterThan, "TrackingNumber", pageKey);
                searchClause = searchClause == null ? start : factory.IntersectClauses(searchClause, start);
            }

            var searchOptions = new Microsoft.CommerceServer.SearchOptions
            {
                PropertiesToReturn = "OrderGroupId,SoldToId",
                NumberOfRecordsToReturn = maxCount,
                SortDescending = false,
                SortProperties = "TrackingNumber"
            };

            var orderIds = new List<string>();
            int recordsMatched;
            using (var ds = manager.SearchPurchaseOrders(searchClause, searchOptions, out recordsMatched))
            {
                foreach (DataRow row in ds.Tables["PurchaseOrders"].Rows)
                {
                    var userId = (Guid)row["SoldToId"];
                    var orderGroupId = (Guid)row["OrderGroupId"];
                    var csPurchaseOrder = OrderContext.GetPurchaseOrder(userId, orderGroupId);
                    foreach (OrderForm csOrderForm in csPurchaseOrder.OrderForms)
                    {
                        orderIds.Add(BuildOrderId(csOrderForm));
                    }
                }
            }

            if (orderIds.Count == 0)
                return null;

            var result = new OrderPage();
            result.Key = orderIds[orderIds.Count - 1];
            result.Orders = orderIds.ToArray();
            result.Remaining = recordsMatched - orderIds.Count;
            return result;
        }

        /// <summary>
        /// Return one shipment from the store.
        /// </summary>
        /// <param name="orderId">Ignored by this implementation.</param>
        /// <param name="packageId">The AbleCommerce OrderShipment ID.</param>
        /// <returns>The Shipment corresponding to the AC OrderShipment.</returns>
        protected static PackageResult GetPackageFromStore(string orderId, string packageId)
        {
            Guid csUserId;
            Guid csOrderGroupId;
            Guid csOrderFormId;
            try
            {
                var csIds = orderId.Split(',');
                if (csIds.Length < 3)
                    throw new ApplicationException("Invalid order ID.");

                csUserId = new Guid(csIds[0]);
                csOrderGroupId = new Guid(csIds[1]);
                csOrderFormId = new Guid(csIds[2]);
            }
            catch (FormatException)
            {
                throw new ApplicationException("Invalid order ID.");
            }
            catch (OverflowException)
            {
                throw new ApplicationException("Invalid order ID.");
            }

            Guid csShipmentId;
            try
            {
                csShipmentId = new Guid(packageId);
            }
            catch (FormatException)
            {
                throw new ApplicationException("Invalid package ID.");
            }
            catch (OverflowException)
            {
                throw new ApplicationException("Invalid package ID.");
            }

            var csPurchaseOrder = OrderContext.GetPurchaseOrder(csUserId, csOrderGroupId);
            if (csPurchaseOrder == null)
                throw new ApplicationException("Invalid user or purchase order ID.");

            var index = csPurchaseOrder.OrderForms.IndexOf(csOrderFormId);
            if (index == -1)
                throw new ApplicationException("Invalid order form ID.");

            var csOrderForm = csPurchaseOrder.OrderForms[index];

            index = csOrderForm.Shipments.IndexOf(csShipmentId);
            if (index == -1)
                throw new ApplicationException("Invalid shipment ID.");

            return BuildPackageResult(csOrderForm.Shipments[index]);
        }

        protected static RateProvider[] GetRateProvidersFromStore()
        {
            var dictionary = new Dictionary<string, List<string>>();
            foreach (ShippingMethod csShippingMethod in OrderManagementContext.ShippingMethodManager.ShippingMethods)
            {
                var rateProviderName = csShippingMethod.GetDescription();
                if (string.IsNullOrEmpty(rateProviderName)) continue;

                if (!dictionary.ContainsKey(rateProviderName))
                {
                    dictionary.Add(rateProviderName, new List<string>());
                }

                var rateOptions = dictionary[rateProviderName];
                var rateOptionName = csShippingMethod.GetShippingMethodName();
                if (!rateOptions.Contains(rateOptionName))
                {
                    rateOptions.Add(rateOptionName);
                }
            }

            var rateProviders = new List<RateProvider>();
            foreach (var pair in dictionary)
            {
                var rateProvider = new RateProvider();
                rateProvider.Name = pair.Key;
                rateProvider.RateOptions = pair.Value.ToArray();
                rateProviders.Add(rateProvider);
            }

            return rateProviders.Count == 0 ? null : rateProviders.ToArray();
        }

        protected static TrackingProvider[] GetTrackingProvidersFromStore()
        {
            return null;
        }

        protected static UpdateResult UpdatePackageInStore(Update update)
        {
            Guid csUserId;
            Guid csOrderGroupId;
            Guid csOrderFormId;
            try
            {
                var csIds = update.OrderId.Split(',');
                if (csIds.Length < 3)
                    throw new ApplicationException("Invalid order ID.");

                csUserId = new Guid(csIds[0]);
                csOrderGroupId = new Guid(csIds[1]);
                csOrderFormId = new Guid(csIds[2]);
            }
            catch (FormatException)
            {
                throw new ApplicationException("Invalid order ID.");
            }
            catch (OverflowException)
            {
                throw new ApplicationException("Invalid order ID.");
            }

            Guid csShipmentId;
            try
            {
                csShipmentId = new Guid(update.PackageId);
            }
            catch (FormatException)
            {
                throw new ApplicationException("Invalid package ID.");
            }
            catch (OverflowException)
            {
                throw new ApplicationException("Invalid package ID.");
            }

            var csPurchaseOrder = OrderContext.GetPurchaseOrder(csUserId, csOrderGroupId);
            if (csPurchaseOrder == null)
                throw new ApplicationException("Invalid user or purchase order ID.");

            var index = csPurchaseOrder.OrderForms.IndexOf(csOrderFormId);
            if (index == -1)
                throw new ApplicationException("Invalid order form ID.");

            var csOrderForm = csPurchaseOrder.OrderForms[index];

            index = csOrderForm.Shipments.IndexOf(csShipmentId);
            if (index == -1)
                throw new ApplicationException("Invalid shipment ID.");

            return BuildUpdateResult(update, csOrderForm.Shipments[index]);
        }

        #endregion

        private const string HANDLING = "Handling";
        private const string SHIPPING = "Shipping";
        private const string SHIPPING_DISCOUNT = "Shipping Discount";
        private const string TAXES = "Taxes";
        private const string ITEM_DISCOUNT_NAME = "Line Discount";
        private const string ORDER_DISCOUNT_NAME = "Order Discount";

        private static bool AddressesAreEqual(OrderAddress csAddress, ShippingAddress shippingAddress)
        {
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.City, shippingAddress.City, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.CountryCode, shippingAddress.Country, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.DaytimePhoneNumber, shippingAddress.Phone, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.Email, shippingAddress.Email, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.FaxNumber, shippingAddress.Fax, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.FirstName, shippingAddress.FirstName, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.LastName, shippingAddress.LastName, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.Line1, shippingAddress.Line1, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.Line2, shippingAddress.Line2, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.Organization, shippingAddress.Company, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.PostalCode, shippingAddress.PostalCode, StringComparison.CurrentCulture)) return false;
            if (!ShippingAgentHelper.StringsAreEqual(csAddress.RegionCode, shippingAddress.Region, StringComparison.CurrentCulture)) return false;

            var residential = ShippingAgentHelper.ConvertToBoolean(csAddress[ShippingAgentConfig.OrderAddressProperties.Residential], false);
            return residential == shippingAddress.Residential;
        }

        private static Address BuildAddress(OrderAddress csAddress, string email)
        {
            var address = new Address();
            address.AddressId = csAddress.OrderAddressId;
            address.City = ShippingAgentHelper.StringOrNull(csAddress.City);
            address.Company = ShippingAgentHelper.StringOrNull(csAddress.Organization);
            address.Country = ShippingAgentHelper.StringOrNull(csAddress.CountryCode);
            address.Email = ShippingAgentHelper.StringOrNull(email);
            address.Fax = ShippingAgentHelper.StringOrNull(csAddress.FaxNumber);
            address.FirstName = ShippingAgentHelper.StringOrNull(csAddress.FirstName);
            address.LastName = ShippingAgentHelper.StringOrNull(csAddress.LastName);
            address.Line1 = ShippingAgentHelper.StringOrNull(csAddress.Line1);
            address.Line2 = ShippingAgentHelper.StringOrNull(csAddress.Line2);
            address.Phone = ShippingAgentHelper.StringOrNull(csAddress.DaytimePhoneNumber);
            address.PostalCode = ShippingAgentHelper.StringOrNull(csAddress.PostalCode);
            address.Region = ShippingAgentHelper.StringOrNull(csAddress.RegionCode);
            return address;
        }

        private static Customer BuildCustomer(Guid csUserId)
        {
            var profile = ProfileContext.GetProfile(csUserId.ToString("B"), "UserObject");
            if (profile == null)
                return null;

            var customer = new Customer();
            customer.CustomerId = csUserId.ToString();
            customer.Email = (string)profile.PropertyGroups["GeneralInfo"]["email_address"].Value;
            customer.FirstName = (string)profile.PropertyGroups["GeneralInfo"]["first_name"].Value;
            customer.LastName = (string)profile.PropertyGroups["GeneralInfo"]["last_name"].Value;

            return customer;
        }

        private static decimal BuildInsuranceValue(IEnumerable<LineItem> csItems)
        {
            decimal value = 0;
            foreach (var csItem in csItems)
            {
                var shippable = ShippingAgentHelper.ConvertToBoolean(csItem[ShippingAgentConfig.LineItemProperties.Shippable], true);
                if (shippable)
                    value += csItem.Quantity*csItem.PlacedPrice;
            }
            return value;
        }

        private static ItemResult BuildItemResult(LineItem csItem)
        {
            var itemResult = new ItemResult();
            itemResult.Item = new Item
                       {
                           Code = ShippingAgentHelper.StringOrNull(csItem.ProductId),
                           Inventory = BuildProductInventory(csItem),
                           ItemId = csItem.LineItemId.ToString(),
                           LineTotal = csItem.Quantity*csItem.PlacedPrice,
                           Manufacturer = null,
                           Name = ShippingAgentHelper.StringOrNull(csItem.DisplayName),
                           Price = csItem.PlacedPrice,
                           Quantity = Convert.ToDouble(csItem.Quantity),
                           Shippable = ShippingAgentHelper.ConvertToBoolean(csItem[ShippingAgentConfig.LineItemProperties.Shippable], true),
                           Vendor = null,
                           Weight = ShippingAgentHelper.ConvertToDouble(csItem[ShippingAgentConfig.LineItemProperties.Weight])
                       };
            itemResult.Success = true;
            return itemResult;
        }

        private static ItemResult BuildItemResult(DiscountApplicationRecord csDiscount, string csDiscountType)
        {
            var itemResult = new ItemResult();
            itemResult.Item = new Item
            {
                Code = string.IsNullOrEmpty(csDiscount.PromoCode) ? "Discount" : "Coupon",
                ItemId = BuildItemId(csDiscount.LineItemId, csDiscount.DiscountId, csDiscountType),
                LineTotal = -csDiscount.DiscountAmount,
                Name = csDiscount.BasketDisplayMessage,
                Price = -csDiscount.DiscountAmount,
                Quantity = 1,
                Shippable = false
            };
            itemResult.Success = true;
            return itemResult;
        }

        private static string BuildItemId(Guid csLineItemId, int csDiscountId, string csDiscountType)
        {
            return string.Format("{0},{1},{2}", csLineItemId, csDiscountId, csDiscountType);
        }

        private static ItemResult[] BuildItemResults(LineItemCollection csLineItems)
        {
            var csItems = new List<LineItem>();
            foreach (LineItem csItem in csLineItems)
            {
                csItems.Add(csItem);
            }
            return BuildItemResults(csItems);
        }

        private static ItemResult[] BuildItemResults(IEnumerable<LineItem> csLineItems)
        {
            var itemResults = new List<ItemResult>();

            foreach (LineItem csItem in csLineItems)
            {
                itemResults.Add(BuildItemResult(csItem));
                foreach (DiscountApplicationRecord csDiscount in csItem.ItemLevelDiscountsApplied)
                {
                    itemResults.Add(BuildItemResult(csDiscount, ITEM_DISCOUNT_NAME));
                }
                foreach (DiscountApplicationRecord csDiscount in csItem.OrderLevelDiscountsApplied)
                {
                    itemResults.Add(BuildItemResult(csDiscount, ORDER_DISCOUNT_NAME));
                }
            }

            return itemResults.Count == 0 ? null : itemResults.ToArray();
        }

        private static OrderResult BuildOrderResult(OrderForm csOrderForm)
        {
            var order = new Order();
            var csAddresses = csOrderForm.ParentOrderGroup.Addresses;

            order.OrderId = BuildOrderId(csOrderForm);

            try
            {
                order.BillingAddress = csOrderForm.Payments.Count > 0 
                    ? BuildAddress(csAddresses[csOrderForm.Payments[0].BillingAddressId], null) 
                    : BuildAddress(csAddresses[csOrderForm.ParentOrderGroup.SoldToAddressId], csOrderForm.ParentOrderGroup.SoldToName);
                order.Charges = BuildOrderCharges(csOrderForm);
                order.Customer = BuildCustomer(csOrderForm.ParentOrderGroup.SoldToId);
                order.CustomerUrl = null;
                order.Items = BuildItemResults(csOrderForm.LineItems);
                order.LastUpdated = csOrderForm.LastModified;
                order.MerchantUrl = null;
                order.OrderDate = csOrderForm.Created;
                order.OrderNumber = BuildOrderNumber(csOrderForm);
                order.OrderStatus = ShippingAgentHelper.SplitCompoundName(csOrderForm.ParentOrderGroup.Status);
                order.OrderTotal = csOrderForm.Total;
                order.Packages = BuildPackageRefs(csOrderForm);
                order.Payments = BuildPayments(csOrderForm);
                order.PaymentStatus = BuildOrderPaymentStatus(order);
                order.ShippingStatus = BuildOrderShippingStatus(csOrderForm);

                return new OrderResult { Success = true, Order = order };
            }
            catch (ApplicationException ex)
            {
                return new OrderResult { Success = false, Message = ex.Message, Order = order };
            }
            catch (Exception ex)
            {
                return new OrderResult { Success = false, Message = ex.ToString(), Order = order };
            }
        }

        private static NamedAmount[] BuildOrderCharges(OrderForm csOrderForm)
        {
            var charges = new NamedAmountList();
            if (csOrderForm.HandlingTotal != 0)
                charges[HANDLING].Amount += csOrderForm.HandlingTotal;
            foreach (Shipment csShipment in csOrderForm.Shipments)
            {
                var shippingCost = csShipment.ShipmentTotal + csShipment.ShippingDiscountAmount;
                if (shippingCost != 0)
                    charges[SHIPPING, csShipment.ShippingMethodName].Amount += shippingCost;
                foreach (ShippingDiscountRecord discount in csShipment.ShippingDiscounts)
                {
                    charges[SHIPPING_DISCOUNT, discount.BasketDisplayMessage].Amount += -discount.DiscountAmount;
                }
            }
            if (csOrderForm.TaxTotal != 0)
                charges[TAXES].Amount += csOrderForm.TaxTotal;
            return charges.Count > 0 ? charges.ToArray() : null;
        }

        private static string BuildOrderId(OrderForm csOrderForm)
        {
            return string.Format("{0},{1},{2}", csOrderForm.ParentOrderGroup.SoldToId, csOrderForm.OrderGroupId, csOrderForm.OrderFormId);
        }

        private static string BuildOrderNumber(OrderForm csOrderForm)
        {
            return csOrderForm.ParentOrderGroup.TrackingNumber + (string.IsNullOrEmpty(csOrderForm.Name) ? string.Empty : " / " + csOrderForm.Name);
        }

        private static string BuildOrderPaymentStatus(Order order)
        {
            if (order.Payments == null || order.Payments.Length == 0)
                return ShippingAgentConfig.OrderPaymentStatuses.Unpaid;

            var total = order.OrderTotal;
            foreach (var amount in order.Payments)
                total -= amount.Amount;

            if (total < 0)
                return ShippingAgentConfig.OrderPaymentStatuses.OverPaid;
            return total == 0 
                ? ShippingAgentConfig.OrderPaymentStatuses.FullyPaid 
                : ShippingAgentConfig.OrderPaymentStatuses.PartiallyPaid;
        }

        private static string BuildOrderShippingStatus(OrderForm csOrderForm)
        {
            var total = 0;
            var shipped = 0;

            foreach (Shipment csShipment in csOrderForm.Shipments)
            {
                total++;

                if (string.IsNullOrEmpty(csShipment.Status))
                    continue;

                if (csShipment.Status.Equals(ShippingAgentConfig.ShipmentStatuses.Unshipped, StringComparison.InvariantCultureIgnoreCase))
                    continue;

                if (csShipment.Status.Equals(ShippingAgentConfig.ShipmentStatuses.Shipped, StringComparison.InvariantCultureIgnoreCase))
                {
                    shipped++;
                    continue;
                }

                return ShippingAgentConfig.OrderShipmentStatuses.Unknown;
            }

            if (total == 0)
                return ShippingAgentConfig.OrderShipmentStatuses.FullyShipped;

            if (shipped == 0)
                return ShippingAgentConfig.OrderShipmentStatuses.Unshipped;

            return shipped == total 
                ? ShippingAgentConfig.OrderShipmentStatuses.FullyShipped 
                : ShippingAgentConfig.OrderShipmentStatuses.PartiallyShipped;
        }

        private static NamedAmount[] BuildPackageCharges(Shipment csShipment)
        {
            var charges = new NamedAmountList();
            var shippingCost = csShipment.ShipmentTotal + csShipment.ShippingDiscountAmount;
            if (shippingCost != 0)
                charges[SHIPPING].Amount += shippingCost;
            foreach (ShippingDiscountRecord discount in csShipment.ShippingDiscounts)
            {
                charges[SHIPPING_DISCOUNT, discount.BasketDisplayMessage].Amount += -discount.DiscountAmount;
            }
            return charges.Count > 0 ? charges.ToArray() : null;
        }

        private static PackageResult BuildPackageResult(Shipment csShipment)
        {
            var package = new Package();

            package.OrderId = BuildOrderId(csShipment.ParentOrderForm);
            package.PackageId = csShipment.ShipmentId.ToString();

            try
            {
                var csShippingAddress = csShipment.ParentOrderForm.ParentOrderGroup.Addresses[csShipment.ShippingAddressId];
                var csItems = new List<LineItem>();
                foreach (int index in csShipment.LineItemIndexes)
                {
                    csItems.Add(csShipment.ParentOrderForm.LineItems[index]);
                }

                package.Charges = BuildPackageCharges(csShipment);
                package.CustomerUrl = null;
                package.Destination = BuildShippingAddress(csShippingAddress);
                package.Height = ShippingAgentHelper.ConvertToDouble(csShipment[ShippingAgentConfig.ShipmentProperties.Height]);
                package.Instructions = null;
                package.InsuranceValue = BuildInsuranceValue(csItems);
                package.Items = BuildItemResults(csItems);
                package.Length = ShippingAgentHelper.ConvertToDouble(csShipment[ShippingAgentConfig.ShipmentProperties.Length]);
                package.MerchantUrl = null;
                package.RateOptionName = ShippingAgentHelper.StringOrNull(csShipment.ShippingMethodName);
                package.RateProviderName = BuildRateProviderName(csShipment);
                var shipDate = ShippingAgentHelper.ConvertToNullableDateTime(csShipment[ShippingAgentConfig.ShipmentProperties.ShipDate]);
                if (shipDate.HasValue)
                {
                    package.ShipDate = shipDate.Value;
                    package.ShipDateSpecified = true;
                }
                else
                {
                    package.ShipDateSpecified = false;
                }
                package.Shipped = csShipment.Status.Equals(ShippingAgentConfig.ShipmentStatuses.Shipped, StringComparison.InvariantCultureIgnoreCase);
                package.TrackingNumber = csShipment.ShipmentTrackingNumber;
                package.TrackingProviderId = null;
                package.TrackingProviderName = null;
                package.Warehouse = null;
                package.Weight = ShippingAgentHelper.ConvertToDouble(csShipment[ShippingAgentConfig.ShipmentProperties.Weight]);
                if (package.Weight == 0) package.Weight = BuildPackageWeight(csItems);
                package.Width = ShippingAgentHelper.ConvertToDouble(csShipment[ShippingAgentConfig.ShipmentProperties.Width]);

                return new PackageResult { Success = true, Package = package };
            }
            catch (ApplicationException ex)
            {
                return new PackageResult { Success = false, Message = ex.Message, Package = package };
            }
            catch (Exception ex)
            {
                return new PackageResult { Success = false, Message = ex.ToString(), Package = package };
            }
        }

        private static PackageRef[] BuildPackageRefs(OrderForm csOrderForm)
        {
            var packageRefs = new List<PackageRef>();

            foreach (Shipment csShipment in csOrderForm.Shipments)
            {
                var packageRef = new PackageRef();
                packageRef.OrderId = BuildOrderId(csOrderForm);
                packageRef.PackageId = csShipment.ShipmentId.ToString();
                packageRefs.Add(packageRef);
            }

            return packageRefs.Count == 0 ? null : packageRefs.ToArray();
        }

        private static double BuildPackageWeight(IEnumerable<LineItem> csItems)
        {
            var weight = 0.0;
            foreach (var csItem in csItems)
            {
                weight += ShippingAgentHelper.ConvertToDouble(csItem[ShippingAgentConfig.LineItemProperties.Weight]);
            }
            return weight;
        }

        /// <summary>
        /// Build a list of payments from a Commerce Server order form.
        /// </summary>
        /// <param name="csOrderForm">The order form.</param>
        /// <returns>The list of payments represented as NamedAmounts.</returns>
        private static NamedAmount[] BuildPayments(OrderForm csOrderForm)
        {
            var payments = new NamedAmountList();

            foreach (Payment csPayment in csOrderForm.Payments)
            {
                // Shipper does not have payment processing features.
                // Only send paid payments.
                if (!string.IsNullOrEmpty(csPayment.Status) 
                    && !csPayment.Status.Equals(ShippingAgentConfig.PaymentStatuses.Paid, StringComparison.InvariantCultureIgnoreCase))
                    continue;

                var payment = new NamedAmount();
                payment.Amount = csPayment.Amount;
                payment.Description = csPayment.PaymentMethodName;
                payment.Name = ShippingAgentHelper.SplitCompoundName(csPayment.DerivedClassName);
                payments.Add(payment);
            }

            return payments.Count > 0 ? payments.ToArray() : null;
        }

        private static double BuildProductInventory(LineItem csItem)
        {
            if (!CatalogContext.InventorySystemExists)
                return 0;

            var productCatalogName = csItem.ProductCatalog;
            var productId = csItem.ProductId;
            var variantId = csItem.ProductVariantId;

            var inventoryCatalog = CatalogContext.InventoryContext.GetAssociatedInventoryCatalog(productCatalogName);
            var sku = string.IsNullOrEmpty(variantId)
                          ? inventoryCatalog.GetSku(productCatalogName, productId)
                          : inventoryCatalog.GetSku(productCatalogName, productId, variantId);
            return Convert.ToDouble(sku.Quantity);
        }

        private static string BuildRateProviderName(Shipment csShipment)
        {
            var shippingGroupId = csShipment["ShippingGroupId"];
            if (shippingGroupId == null)
                return null;

            if (shippingGroupId is Guid)
            {
                var index = OrderManagementContext.ShippingMethodManager.ShippingMethods.IndexOf((Guid) shippingGroupId);
                if (index == -1)
                    return null;

                var shippingMethod = OrderManagementContext.ShippingMethodManager.ShippingMethods[index];
                return shippingMethod.GetDescription();
            }

            return null;
        }

        private static ShippingAddress BuildShippingAddress(OrderAddress csAddress)
        {
            var address = new ShippingAddress();
            address.AddressId = csAddress.OrderAddressId;
            address.City = ShippingAgentHelper.StringOrNull(csAddress.City);
            address.Company = ShippingAgentHelper.StringOrNull(csAddress.Organization);
            address.Country = ShippingAgentHelper.StringOrNull(csAddress.CountryCode);
            address.Email = null;
            address.Fax = ShippingAgentHelper.StringOrNull(csAddress.FaxNumber);
            address.FirstName = ShippingAgentHelper.StringOrNull(csAddress.FirstName);
            address.LastName = ShippingAgentHelper.StringOrNull(csAddress.LastName);
            address.Line1 = ShippingAgentHelper.StringOrNull(csAddress.Line1);
            address.Line2 = ShippingAgentHelper.StringOrNull(csAddress.Line2);
            address.Phone = ShippingAgentHelper.StringOrNull(csAddress.DaytimePhoneNumber);
            address.PostalCode = ShippingAgentHelper.StringOrNull(csAddress.PostalCode);
            address.Region = ShippingAgentHelper.StringOrNull(csAddress.RegionCode);
            address.Residential = ShippingAgentHelper.ConvertToBoolean(csAddress[ShippingAgentConfig.OrderAddressProperties.Residential], false);
            return address;
        }

        private static UpdateResult BuildUpdateResult(Update update, Shipment csShipment)
        {
            var notes = new List<string>();

            if (update.Destination != null)
            {
                var csAddress = csShipment.ParentOrderForm.ParentOrderGroup.Addresses[csShipment.ShippingAddressId];
                
                if (!AddressesAreEqual(csAddress, update.Destination))
                {
                    if (!csAddress.CountryCode.Equals(update.Destination.Country, StringComparison.CurrentCultureIgnoreCase))
                        csAddress.CountryName = string.Empty;
                    if (!csAddress.RegionCode.Equals(update.Destination.Region, StringComparison.CurrentCultureIgnoreCase))
                        csAddress.RegionName = string.Empty;

                    csAddress.Line1 = update.Destination.Line1;
                    csAddress.Line2 = update.Destination.Line2;
                    csAddress.City = update.Destination.City;
                    csAddress.Organization = update.Destination.Company;
                    csAddress.CountryCode = update.Destination.Country;
                    csAddress.Email = update.Destination.Email;
                    csAddress.FaxNumber = update.Destination.Fax;
                    csAddress.FirstName = update.Destination.FirstName;
                    csAddress.LastName = update.Destination.LastName;
                    csAddress.DaytimePhoneNumber = update.Destination.Phone;
                    csAddress.PostalCode = update.Destination.PostalCode;
                    csAddress.RegionCode = update.Destination.Region;
                    csAddress[ShippingAgentConfig.OrderAddressProperties.Residential] = update.Destination.Residential;
                    try
                    {
                        ((PurchaseOrder)csShipment.ParentOrderForm.ParentOrderGroup).Save(ShippingAgentConfig.ApplicationId);
                        notes.Add(string.Format("Updated shipping address for shipment '{0}'.", csShipment.ShipmentId));
                    }
                    catch (Exception ex)
                    {
                        return new UpdateResult { Success = false, Message = "Cannot save shipping address. " + ex.Message };
                    }
                }
            }

            if (update.TrackingNumber != null)
            {
                if (!csShipment.ShipmentTrackingNumber.Equals(update.TrackingNumber, StringComparison.InvariantCultureIgnoreCase))
                {
                    try
                    {
                        csShipment.ShipmentTrackingNumber = update.TrackingNumber;
                        ((PurchaseOrder)csShipment.ParentOrderForm.ParentOrderGroup).Save(ShippingAgentConfig.ApplicationId);
                        notes.Add(string.Format("Added tracking number {0}.", update.TrackingNumber));
                    }
                    catch (Exception ex)
                    {
                        return new UpdateResult { Success = false, Message = "Cannot save shipping address. " + ex.Message };
                    }
                }
            }

            if (update.ShipDateSpecified)
            {
                var shipDate = ShippingAgentHelper.ConvertToNullableDateTime(csShipment[ShippingAgentConfig.ShipmentProperties.ShipDate]);
                if (shipDate != update.ShipDate)
                {
                    csShipment[ShippingAgentConfig.ShipmentProperties.ShipDate] = update.ShipDate;
                    csShipment.Status = ShippingAgentConfig.ShipmentStatuses.Shipped;
                    try
                    {
                        ((PurchaseOrder)csShipment.ParentOrderForm.ParentOrderGroup).Save(ShippingAgentConfig.ApplicationId);
                        notes.Add(string.Format("Shipped shipment {0}.", csShipment.ShipmentId));
                    }
                    catch (Exception ex)
                    {
                        return new UpdateResult { Success = false, Message = "Cannot ship shipment. " + ex.Message };
                    }
                }
            }

            if (update.ShippingCostSpecified)
            {
                notes.Add(string.Format("Actual shipping cost is {0:c}.", update.ShippingCost));
            }

            if (notes.Count > 0)
            {
                var result = new UpdateResult();
                result.OrderResult = BuildOrderResult(csShipment.ParentOrderForm);
                result.PackageResult = BuildPackageResult(csShipment);
                result.Message = string.Join(" ", notes.ToArray());
                result.Success = true;
                return result;
            }

            return new UpdateResult { Success = true, Message = "No change." };
        }

        private static CatalogContext _catalogContext;
        private static CatalogContext CatalogContext
        {
            get
            {
                if (_catalogContext == null)
                {
                    var agent = new CatalogSiteAgent();
                    agent.SiteName = ShippingAgentConfig.SiteName;
                    agent.IgnoreInventorySystem = ShippingAgentConfig.IgnoreInventorySystem;
                    _catalogContext = CatalogContext.Create(agent);
                }
                return _catalogContext;
            }
        }

        private static ShippingAgentSection ShippingAgentConfig
        {
            get
            {
                var config = (ShippingAgentSection)ConfigurationManager.GetSection("shippingAgent");
                if (config == null)
                {
                    throw new Exception("The <shippingAgent> section was not found in web.config");
                }
                return config;
            }
        }

        private static OrderContext _orderContext;
        private static OrderContext OrderContext
        {
            get
            {
                if (_orderContext == null)
                {
                    _orderContext = OrderContext.Create(ShippingAgentConfig.SiteName);
                }
                return _orderContext;
            }
        }

        private static OrderManagementContext _orderManagementContext;
        private static OrderManagementContext OrderManagementContext
        {
            get
            {
                if (_orderManagementContext == null)
                {
                    var agent = new OrderSiteAgent(ShippingAgentConfig.SiteName);
                    _orderManagementContext = OrderManagementContext.Create(agent);
                }
                return _orderManagementContext;
            }
        }

        private static ProfileContext _profileContext;
        private static ProfileContext ProfileContext
        {
            get
            {
                if (_profileContext == null)
                {
                    var resources = new CommerceResourceCollection(ShippingAgentConfig.SiteName);
                    var connectionString = (string)resources["Biz Data Service"]["s_ProfileServiceConnectionString"];
                    _profileContext = new ProfileContext(connectionString, new WebDebugContext(DebugMode.Production));
                }
                return _profileContext;
            }
        }
    }
}